// SPDX-FileCopyrightText: 2022-2025 CASTest Corporation Limited
// SPDX-FileCopyrightText: 2022-2025 Institute of Computing Technology, Chinese Academy of Sciences
// SPDX-License-Identifier: LGPL-v3

#include "FaultList.h"

void FaultList::CreateCellFaultList() {
    Gate*   gptr = nullptr;
    Fault*  fptr = nullptr;
    int     fault_idx = 0;

    for(int v = _levels.size()-1; v >= 0; v--) {
        for(int idx = 0; idx < _levels[v].size(); idx++) {
            gptr = _levels[v][idx];
            assert(gptr != nullptr);
            for(int sa = 0; sa < 2; sa++) {
                CheckEqvRelation(gptr, sa);
            }
        }
    }

#if 0
    int count = 0;
    for (auto it1 = _eqv_group.begin(); it1 != _eqv_group.end(); it1++) {
        cout << count << "-> ";
        for (auto it2 = it1->begin(); it2 != it1->end(); it2++) {
            cout << ((*it2) >> 1) << " " << ((*it2) & 1) << " |";
        }
        cout << endl;
        count ++;
    }
#endif

    for(auto it1 = _eqv_group.begin(); it1 != _eqv_group.end(); it1++) {
        bool head = false;
        for(auto it2 = it1->begin(); it2 != it1->end(); it2 ++) {
            gptr = _netlist[(*it2) >> 1];
            assert(gptr != nullptr);
            int sa = (*it2) & 1;
            // only cell border gate can create faults
            if (gptr->IsCellBorder()) {
                // step 0: process supply gate
                // supply has only one fanout and the fanout is not stem.
                // otherwise, stem will process supply fanout faults.
                if ((gptr->GetLogicType() == G_SUPPLY0  ||
                     gptr->GetLogicType() == G_SUPPLY1)) {
                    if (gptr->GetFanout(0)->GetLogicType() != G_STEM) {
                        string cell_pin_name;
                        Gate* fanout = gptr->GetFanout(0);
                        for (int i = 0; i < fanout->GetFaninsSize(); i++) {
                            if (gptr == fanout->GetFanin(i)) {
                                int pin_id = fanout->GetPredPin()[i];
                                cell_pin_name = _pinnames[pin_id];
                                break;
                            }
                        }
                        string fault_name = cell_pin_name;
                        string eqv = (head ? "--":"NC");
                        FaultType ftype = sa ? SA1 : SA0;
                        head = true;
                        fptr = new Fault( fault_idx++, gptr, UNDETECTED, ftype, fault_name, eqv );
                        _uflist.push_back(fptr);
                        gptr->AddFlist(fptr);
                        continue;
                    } else {
                        continue;
                    }
                }
                // Step 1: process BUF_ASSIGN gate
                if (gptr->GetLogicType() == G_BUF_ASSGIN) {
                    int size = gptr->GetFanoutsSize();
                    for (int i = 0; i < size; i++) {
                        if (gptr->GetFanout(i)->GetLogicType() == G_PO) {
                            string fault_name = _netnames[gptr->GetId()];
                            string eqv = (head ? "--":"NC");
                            FaultType ftype = sa ? SA1 : SA0;
                            head = true;
                            fptr = new Fault( fault_idx++, gptr, UNDETECTED, ftype, fault_name, eqv );
                            _uflist.push_back(fptr);
                            gptr->AddFlist(fptr);
                            break;
                        }
                    }
                    continue;
                }
                // Step 1: process G_STEM gate, create faults at its fanout cell's input pin.
                if (gptr->GetLogicType() == G_STEM) {
                    // stem fanout is a po gate
                    if (gptr->GetFanout(0)->GetLogicType() == G_PO) {
                        string fault_name = _netnames[gptr->GetFanin(0)->GetId()];
                        string eqv = (head ? "--":"NC");
                        FaultType ftype = sa ? SA1 : SA0;
                        head = true;
                        fptr = new Fault( fault_idx++, gptr, UNDETECTED, ftype, fault_name, eqv );
                        _uflist.push_back(fptr);
                        gptr->AddFlist(fptr);
                    } else if (gptr->GetFanout(0)->GetLogicType() == G_BUF_ASSGIN) {
                        continue;
                    } else {
                        string cell_pin_name;
                        Gate* fanout = gptr->GetFanout(0);
                        for (int i = 0; i < fanout->GetFaninsSize(); i++) {
                            if (gptr == fanout->GetFanin(i)) {
                                int pin_id = fanout->GetPredPin()[i];
                                cell_pin_name = _pinnames[pin_id];
                                break;
                            }
                        }
                        string fault_name = cell_pin_name;
                        if (fault_name == "supply0" || fault_name == "supply1")
                            continue;
                        string eqv = (head ? "--":"NC");
                        FaultType ftype = sa ? SA1 : SA0;
                        head = true;
                        fptr = new Fault( fault_idx++, gptr, UNDETECTED, ftype, fault_name, eqv );
                        _uflist.push_back(fptr);
                        gptr->AddFlist(fptr);
                    }
                } else {
                    // Step 2: process PI gate, get netname.
                    if (gptr->GetLogicType() == G_PI) {
                        string fault_name = _netnames[gptr->GetId()];
                        string eqv = (head ? "--":"NC");
                        FaultType ftype = sa ? SA1 : SA0;
                        head = true;
                        fptr = new Fault( fault_idx++, gptr, UNDETECTED, ftype, fault_name, eqv );
                        _uflist.push_back(fptr);
                        gptr->AddFlist(fptr);
                    } else if(gptr->GetLogicType() == G_BUF_ASSGIN) {
                        continue;
                    } else {
                        // Step 3: create faults at the cell outputs pin.
                        // gptr has only one succ
                        int pin_id = gptr->GetSuccPin()[0];
                        string cell_pin_name = _pinnames[pin_id];
                        string fault_name = cell_pin_name;
                        string eqv = (head ? "--":"NC");
                        FaultType ftype = sa ? SA1 : SA0;
                        head = true;
                        fptr = new Fault( fault_idx++, gptr, UNDETECTED, ftype, fault_name, eqv );
                        _uflist.push_back(fptr);
                        gptr->AddFlist(fptr);
                    }

                    // Step 4. create faults at cell input pin.
                    // stem is processed in step 1, so current gptr must have only one fanout.
                    int size = gptr->GetFanoutsSize();
                    if (size == 1) {
                        // gptr fanout
                        if (gptr->GetFanout(0)->GetLogicType() == G_BUF_ASSGIN) {
                            continue;
                        } else if (gptr->GetFanout(0)->GetLogicType() == G_PO) {
                            string fault_name = _netnames[gptr->GetId()];
                            string eqv = (head ? "--":"NC");
                            FaultType ftype = sa ? SA1 : SA0;
                            head = true;
                            fptr = new Fault( fault_idx++, gptr, UNDETECTED, ftype, fault_name, eqv );
                            _uflist.push_back(fptr);
                            gptr->AddFlist(fptr);
                        } else {
                            string cell_pin_name;
                            Gate* fanout = gptr->GetFanout(0);
                            for (int i = 0; i < fanout->GetFaninsSize(); i++) {
                                if (gptr == fanout->GetFanin(i)) {
                                    int pin_id = fanout->GetPredPin()[i];
                                    cell_pin_name = _pinnames[pin_id];
                                    break;
                                }
                            }
                            string fault_name = cell_pin_name;
                            string eqv = (head ? "--":"NC");
                            FaultType ftype = sa ? SA1 : SA0;
                            head = true;
                            fptr = new Fault( fault_idx++, gptr, UNDETECTED, ftype, fault_name, eqv );
                            _uflist.push_back(fptr);
                            gptr->AddFlist(fptr);
                        }
                    }
                }
            }
        }
    }
}

void FaultList::CreateCellEqvFaultList() {
    for (auto it = _uflist.begin(); it != _uflist.end(); it++) {
        Fault* fptr = (*it);
        assert(fptr != nullptr);
        if (fptr->GetFaultEqv() == "NC") {
            _cflist.push_back(fptr);
        }
    }
}

void FaultList::CheckEqvRelation(Gate *gptr, int sa) {
    int hash_key = 2*gptr->GetId() + sa;
    if(!_group_id.count(hash_key)) {
        int new_group = _eqv_group.size();
        _group_id[hash_key] = new_group;
        _eqv_group.push_back(list<int>());
        _eqv_group[new_group].push_back(hash_key);
    }

    int id = _group_id[hash_key];
    int new_key;
    switch(gptr->GetLogicType()) {
        case G_PI:
            break;
        case G_PO:
            break;
        case G_BUF:
            new_key = 2*gptr->GetFanin(0)->GetId() + sa;
            _group_id[new_key] = id;
            _eqv_group[id].push_back(new_key);
            break;
        case G_BUF_ASSGIN:
            new_key = 2*gptr->GetFanin(0)->GetId() + sa;
            _group_id[new_key] = id;
            _eqv_group[id].push_back(new_key);
            break;
        case G_STEM:
            break;
        case G_NOT:
            new_key = 2*gptr->GetFanin(0)->GetId() + (1-sa);
            _group_id[new_key] = id;
            _eqv_group[id].push_back(new_key);
            break;
        case G_AND:
            if(sa == 0) {
                for(int fi = 0; fi < gptr->GetFaninsSize(); fi++) {
                    new_key = 2*gptr->GetFanin(fi)->GetId();
                    _group_id[new_key] = id;
                    _eqv_group[id].push_back(new_key);
                }
            }
            break;
        case G_NAND:
            if(sa == 1) {
                for(int fi = 0; fi < gptr->GetFaninsSize(); fi++) {
                    new_key = 2*gptr->GetFanin(fi)->GetId();
                    _group_id[new_key] = id;
                    _eqv_group[id].push_back(new_key);
                }
            }
            break;
        case G_OR:
            if(sa == 1) {
                for(int fi = 0; fi < gptr->GetFaninsSize(); fi++) {
                    new_key = 2*gptr->GetFanin(fi)->GetId() + 1;
                    _group_id[new_key] = id;
                    _eqv_group[id].push_back(new_key);
                }
            }
            break;
        case G_NOR:
            if(sa == 0) {
                for(int fi = 0; fi < gptr->GetFaninsSize(); fi++) {
                    new_key = 2*gptr->GetFanin(fi)->GetId() + 1;
                    _group_id[new_key] = id;
                    _eqv_group[id].push_back(new_key);
                }
            }
            break;
        case G_XOR:
            break;
        case G_XNOR:
            break;
        case G_MUX:
            break;
        case G_DFF:
            break;
        case G_DLAT:
            break;
        case G_SUPPLY0:
            break;
        case G_SUPPLY1:
            break;
        default: {
            string msg = "not supported logic type: " + to_string(gptr->GetLogicType());
            cerr << Errors::ErrorsMsg(ErrorType::FLIST_CREATE_FAILED, msg);
            exit(1);
        }
    }
}

void FaultList::PrintFaultList(ostream &out, vector<Fault *> &flist) {
    Fault* fptr = nullptr;
    for (auto it = flist.begin(); it != flist.end(); it++) {
        fptr = (*it);
        // string name = fptr->GetFaultGate()->GetFanout(0)->GetName();
        // int      id = fptr->GetFaultGate()->GetId();
        string   sa = fptr->GetFaultType() == SA0 ? "sa0" : "sa1";
        // out << name << " ";
        out << sa << "  " << fptr->GetFaultEqv() << "  " << fptr->GetFaultName()<< endl;
    }
    out << endl;
}
